/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.client.java;

import java.io.File;
import java.io.PrintStream;
import java.util.Iterator;

import net.interknit.knitter.client.SourceWriter;
import net.interknit.knitter.client.layout.TypeEl;


/**
 *
 */
public class JavaTypeSourceWriter implements SourceWriter
{
	private TypeEl typeEl;

	@Override
	public void setTypeEl(TypeEl config)
	{
		this.typeEl = config;
	}

	public TypeEl getTypeEl()
	{
		return this.typeEl;
	}

	private PrintStream out;

	protected String unqualifiedClassName()
	{
		return JavaSourceWriterUtils.unqualifiedClassName(getTypeEl().getName());
	}

	protected String packageName()
	{
		return JavaSourceWriterUtils.packageName(getTypeEl().getName());
	}

	protected File targetSourceDir(File rootSrcDir, String packageName)
	{
		File targetSourceDir = JavaSourceWriterUtils.packageSourceDir(
				rootSrcDir,
				packageName,
				getTypeEl().getName());

		targetSourceDir.mkdirs();

		return targetSourceDir;
	}

	@Override
	public void write(File srcDir, String packagename)
	{
		try
		{
			File srcFile = new File(targetSourceDir(srcDir, packagename), unqualifiedClassName() + ".java");
			this.out = new PrintStream(srcFile);

			println("/* !!! GENERATED SOURCE FILE, DO NOT MODIFY !!! */");

			writeCode(packagename);

			System.out.println(srcFile.getAbsolutePath() + " written successfully.");
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
		finally
		{
			if(out != null) out.close();
		}
	}

	protected void writeCode(String packageName)
	{
		if(! "".equals(packageName.trim()))
			println("package " + packageName + ";");

		println("import javax.xml.bind.annotation.XmlRootElement;");

		print("@XmlRootElement(name = \"" + getTypeEl().getName() + "\")");
		print("public class " + unqualifiedClassName());
		print("{");

		for(Iterator<String> it = typeEl.getFields().keySet().iterator(); it.hasNext(); )
		{
			String pname = it.next();
			String ptype = typeEl.getFields().get(pname);

			ptype = JavaSourceWriterUtils.toJavaType(ptype);

			print(JavaSourceWriterUtils.fieldDeclarationSource(pname, ptype));
		}

		for(Iterator<String> it = typeEl.getFields().keySet().iterator(); it.hasNext(); )
		{
			String pname = it.next();
			String ptype = typeEl.getFields().get(pname);

			ptype = JavaSourceWriterUtils.toJavaType(ptype);

			print("");
			print(JavaSourceWriterUtils.getterSource(pname, ptype));
			print("");
			print(JavaSourceWriterUtils.setterSource(pname, ptype));
		}

		print("");
		print("\t" + "public String toString()");
		print("\t" + "{");
		print("\t" + "\t" + "return");
		boolean first = true;
		for(Iterator<String> it = typeEl.getFields().keySet().iterator(); it.hasNext(); )
		{
			String pname = it.next();

			print("\t" + "\t" + "\t" + (first ? "" : "+ ") + "\"" + pname + ": \" + " + "\"[\" + String.valueOf(this." + pname + ") + \"], \"");
			first = false;
		}
		print("\t" + "\t" + ";");
		print("\t" + "}");
		print("");

		if("Object".equals(getTypeEl().getName()))
		{
			print("");
			print("\t" + "public <T> Object serialize(Class<T> clazz, T object)");
			print("\t" + "{");
			print("\t" + "\t" + "setValue(new WireAdapter<T>(clazz).serialize(object));");
			print("");
			print("\t" + "\t" + "return this;");
			print("\t" + "}");
			print("");
			print("\t" + "public <T> T deserialize(Class<T> clazz)");
			print("\t" + "{");
			print("\t" + "\t" + "return new WireAdapter<T>(clazz).deserialize(getValue());");
			print("\t" + "}");
		}

		if("File".equals(getTypeEl().getName()))
		{
			print("\t" + "/**");
			print("\t" + " * Write the contents (bytes) of this object to a physical file at the given absolute path. If the given");
			print("\t" + " * filePath value is null, an attempt will be made to write the file at the same absolute path that was");
			print("\t" + " * received from the remote location. This method will also ensure that the parent directories are created");
			print("\t" + " * before the file is written. This method may throw a RuntimeException if either the file cannot be");
			print("\t" + " * written at the given path, or if the path received from the remote location is null or non-existent, or");
			print("\t" + " * if the directories could not be created to write the file to.");
			print("\t" + " * ");
			print("\t" + " * @param filePath");
			print("\t" + " *     Absolute path where the bytes of this object should be written to as a physical file. If the");
			print("\t" + " *     given filePath value is null, an attempt will be made to write the file at the same absolute");
			print("\t" + " *     path that was received from the remote location.");
			print("\t" + " * @return newly created physical file object");
			print("\t" + " */");
			print("\t" + "public java.io.File writeTo(java.lang.String filePath)");
			print("\t" + "{");
			print("\t" + "\t" + "try");
			print("\t" + "\t" + "{");
			print("\t" + "\t" + "\t" + "java.io.File file = new java.io.File(filePath != null ? filePath : getPath());");
			print("");
			print("\t" + "\t" + "\t" + "file.getParentFile().mkdirs();");
			print("");
			print("\t" + "\t" + "\t" + "java.io.FileOutputStream out = new java.io.FileOutputStream(file);");
			print("\t" + "\t" + "\t" + "out.write(getValue());");
			print("\t" + "\t" + "\t" + "out.close();");
			print("");
			print("\t" + "\t" + "\t" + "return file;");
			print("\t" + "\t" + "}");
			print("\t" + "\t" + "catch(java.lang.Exception e)");
			print("\t" + "\t" + "{");
			print("\t" + "\t" + "\t" + "throw new java.lang.RuntimeException(e);");
			print("\t" + "\t" + "}");
			print("\t" + "}");
			print("");
			print("\t" + "/**");
			print("\t" + " * Read the bytes from the given physical file into this object before sending this object for the remote");
			print("\t" + " * method invocation.");
			print("\t" + " * ");
			print("\t" + " * @param filePath");
			print("\t" + " *     Absolute path of the physical file that will be read into this object");
			print("\t" + " * @return this object");
			print("\t" + " */");
			print("\t" + "public File readFrom(String filePath)");
			print("\t" + "{");
			print("\t" + "\t" + "try");
			print("\t" + "\t" + "{");
			print("\t" + "\t" + "\t" + "java.io.File file = new java.io.File(filePath);");
			print("\t" + "\t" + "\t" + "java.io.InputStream is = new java.io.FileInputStream(file);");
			print("");
			print("\t" + "\t" + "\t" + "byte[] bytes = new byte[is.available()];");
			print("\t" + "\t" + "\t" + "is.read(bytes);");
			print("\t" + "\t" + "\t" + "is.close();");
			print("");
			print("\t" + "\t" + "\t" + "setPath(file.getAbsolutePath());");
			print("\t" + "\t" + "\t" + "setValue(bytes);");
			print("");
			print("\t" + "\t" + "\t" + "return this;");
			print("\t" + "\t" + "}");
			print("\t" + "\t" + "catch(java.lang.Exception e)");
			print("\t" + "\t" + "{");
			print("\t" + "\t" + "\t" + "throw new java.lang.RuntimeException(e);");
			print("\t" + "\t" + "}");
			print("\t" + "}");
		}

		print("}");
	}

	protected final void println(String line)
	{
		print(line);
		out.println();
	}

	protected final void print(String line)
	{
		out.println(line);
	}
}
